/************************************************
 * MirrorOS Kernel Drivers
 * Fat FS Driver
 * Copyright 2008 Matthew Iselin
 ***********************************************/

// Includes
#include "fat.h"

// frees memory from the heap
void free( void* ptr );

// Variables

// all installed FAT filesystems
struct fat_fs fats[10];

// the descriptor table
struct intdesc desctable[4096]; // file i/o is the most important thing we'll be doing - we need to cater for greedy processes

// Functions

// gets the first sector of a cluster
unsigned int first_sector_of_cluster( struct fat_fs fs, unsigned int clus )
{
	// this is an easy calculation
	return ((clus - 2) * fs.bpb.BPB_SecPerClus) + fs.data_area_start;
}

// gets the data for a specific cluster in the FAT (NOT the next cluster, the DATA in the FAT for that entry)
unsigned int get_clus_entry( struct fat_fs fs, unsigned int clus )
{
	// calculate the size of the FAT
	unsigned int FATSz = 0;
	if( fs.bpb.BPB_FATSz16 != 0 )
		FATSz = fs.bpb.BPB_FATSz16;
	else
		FATSz = fs.bpb32.BPB_FATSz32;
	
	// calculate the offset into the FAT
	unsigned int fat_offset;
	if( fs.fstype == FATFS_12 )
		fat_offset = clus + (clus / 2); // integer division rounds down, FAT12 entries are 1.5 bytes
	else if( fs.fstype == FATFS_16 )
		fat_offset = clus * 2;
	else
		fat_offset = clus * 4;
	
	// calculate the sector number and the offset into the FAT
	unsigned int ThisFATSecNum = fs.bpb.BPB_RsvdSecCnt + (fat_offset / fs.bpb.BPB_BytsPerSec);
	unsigned int ThisFATEntOffset = fat_offset % fs.bpb.BPB_BytsPerSec;
	
	// the sector count to read in
	unsigned int sec_count = 1;
	if( fs.fstype == FATFS_12 )
		sec_count = 2; // so we don't *need* to do any boundary checks
	
	// open and read from the device
	int fd = sys_open( fs.mount, 0 ); // fs.desc - actually this MAY be slow, but it works, which is more important
	if( fd == -1 )
		return 0;
	struct stat s;
	sys_stat( fd, &s );
	char* dat = (char*) malloc( s.st_blksize * sec_count );
	memset(dat, 0, s.st_blksize * sec_count);
	if( !dat )
	{
		sys_close( fd );
		return 0;
	}
	sys_seek( fd, ThisFATSecNum, SEEK_SET );
	int numread = sys_read( fd, dat, sec_count );
	sys_close( fd );
	
	// firstly check that the data we got is right
	if( fd == -1 || numread != sec_count )
	{
		free( dat );
		return 0;
	}
	
	// return the entry
	unsigned int ret = 0;
	switch( fs.fstype )
	{
		case FATFS_12:
			ret = *((unsigned short*) &dat[ThisFATEntOffset]);
			if( clus & 0x1 )
				ret >>= 4;
			else
				ret &= 0x0fff;
			ret &= 0xffff;
			break;

		case FATFS_16:
			ret = (unsigned int) (*((unsigned short*) &dat[ThisFATEntOffset]));
			ret &= 0xFFFF;
			break;
		
		case FATFS_32:
			ret = *((unsigned int*) &dat[ThisFATEntOffset]);
			break;
	}
	
	// free the buffer
	free( dat );
	
	// return whatever we're meant to
	return ret;
}

// sets the data for a specific cluster in the FAT
unsigned int set_clus_entry( struct fat_fs fs, unsigned int clus, unsigned int value )
{
	// calculate the size of the FAT
	unsigned int FATSz = 0;
	if( fs.bpb.BPB_FATSz16 != 0 )
		FATSz = fs.bpb.BPB_FATSz16;
	else
		FATSz = fs.bpb32.BPB_FATSz32;
	
	// calculate the offset into the FAT
	unsigned int fat_offset;
	if( fs.fstype == FATFS_12 )
		fat_offset = clus + (clus / 2); // integer division rounds down, FAT12 entries are 1.5 bytes
	else if( fs.fstype == FATFS_16 )
		fat_offset = clus * 2;
	else
		fat_offset = clus * 4;
	
	// calculate the sector number and the offset into the FAT
	unsigned int ThisFATSecNum = fs.bpb.BPB_RsvdSecCnt + (fat_offset / fs.bpb.BPB_BytsPerSec);
	unsigned int ThisFATEntOffset = fat_offset % fs.bpb.BPB_BytsPerSec;
	
	// the sector count to read in
	unsigned int sec_count = 1;
	if( fs.fstype == FATFS_12 )
		sec_count = 2; // so we don't *need* to do any boundary checks
	
	// open and read from the device
	int fd = sys_open( fs.mount, 0 ); // fs.desc - see get_clus_entry
	if(fd == -1)
		return 0;
	struct stat s;
	sys_stat( fd, &s );
	char* dat = (char*) malloc( s.st_blksize * sec_count );
	if( !dat )
	{
		sys_close( fd );
		return 0;
	}
	sys_seek( fd, ThisFATSecNum, SEEK_SET );
	int numread = sys_read( fd, dat, sec_count );
	
	// firstly check that the data we got is right
	if( numread != sec_count )
	{
		free( dat );
		return 0;
	}
	
	// get the entry
	unsigned int ret = 0;
	switch( fs.fstype )
	{
		case FATFS_12:
			ret = *((unsigned short*) &dat[ThisFATEntOffset]);
			if( clus & 0x1 )
				ret >>= 4;
			else
				ret &= 0x0fff;
			ret &= 0xffff;
			break;

		case FATFS_16:
			ret = (unsigned int) (*((unsigned short*) &dat[ThisFATEntOffset]));
			ret &= 0xFFFF;
			break;
		
		case FATFS_32:
			ret = *((unsigned int*) &dat[ThisFATEntOffset]);
			break;
	}
	
	// calculate the new entry
	switch( fs.fstype )
	{
		case FATFS_12:
			ret = *((unsigned short*) &dat[ThisFATEntOffset]);
			if( clus & 0x1 )
				ret >>= 4;
			else
				ret &= 0x0fff;
			ret &= 0xffff;
			
			if( clus & 0x0001 )
			{
				value <<= 4;
				*((unsigned short*) &dat[ThisFATEntOffset]) &= 0x000F;
			}
			else
			{
				value &= 0x0FFF;
				*((unsigned short*) &dat[ThisFATEntOffset]) &= 0xF000;
			}
			*((unsigned short*) &dat[ThisFATEntOffset]) |= (unsigned short) value;
			break;

		case FATFS_16:
			*((unsigned short*) &dat[ThisFATEntOffset]) = (unsigned short) value;
			break;
		
		case FATFS_32:
			value &= 0x0FFFFFFF;
			*((unsigned int*) &dat[ThisFATEntOffset]) &= 0xF0000000;
			*((unsigned int*) &dat[ThisFATEntOffset]) |= value;
			break;
	}
	
	// rewrite it back
	sys_seek( fd, ThisFATSecNum, SEEK_SET );
	int numwrite = sys_write( fd, dat, sec_count );
	sys_close(fd); // and close it, which was noticeably missing :/
	
	// free the buffer
	free( dat );
	fs.desc = -1;
	
	// return whatever we're meant to
	if( numwrite != sec_count )
		return -1;
	return ret;
}

// checks to see if the given number is a power of 2
int is_power_2( unsigned int n )
{
	unsigned log;

	for(log = 0; log < 16; log++)
	{
		if(n & 1)
		{
			n >>= 1;
			return (n != 0) ? -1 : log;
		}
		n >>= 1;
	}
	return -1;
}

// converts a character from lowercase to uppercase
char char_to_upper( char c )
{
	if( c < 'a' || c > 'z' )
		return c;
	c += ('A' - 'a');
	return c;
}

// converts a character from uppercase to lowercase
char char_to_lower( char c )
{
	if( c < 'A' || c > 'Z' )
		return c;
	c -= ('A' - 'a');
	return c;
}

// converts a string from 8.3 format
char* convert_from_8_3( char* str )
{	
	// return string and internal offset within the return string
	static char ret[32];
	int b = 0;
	
	// initial copy
	int i;
	for( i = 0; i < 8; i++ )
	{
		if( str[i] != ' ' )
			ret[b++] = char_to_lower( str[i] );
		else
			break;
	}
	
	// copy the extension
	for( i = 0; i < 3; i++ )
	{
		if( str[8+i] != ' ' )
		{
			if( i == 0 )
				ret[b++] = '.';
			ret[b++] = char_to_lower( str[8+i] );
		}
		else
			break;
	}
	ret[b++] = 0;
	
	// return the return string (subsequent calls will invalidate the pointer)
	return ret;
}

// converts a string to 8.3 format
char* convert_to_8_3( char* str )
{
	// check that we can work for this string (it must be under or exactly 11 characters long)
	if( strlen( str ) > 11 )
		return 0; // invalid string!
	
	// loop through each character in the filename, creating a valid 8.3 filename instead
	// of whatever's there
	char ret[11];
	int i, z;
	for( i = 0, z = 0; i < 11; i++, z++ )
	{
		// if we're not about to hit an extension, put it into the return string
		if( str[z] != '.' && str[z] != 0 )
			ret[i] = char_to_upper( str[z] );
		else
		{
			// pad with spaces
			for( ; i < 8; i++ )
				ret[i] = ' ';
		
			// copy the extension
			if( str[z] != 0 )
			{
				ret[i] = char_to_upper( str[z+1] );
				ret[i+1] = char_to_upper( str[z+2] );
				ret[i+2] = char_to_upper( str[z+3] );
			}
			else // folder much?
			{
				ret[i] = ' ';
				ret[i+1] = ' ';
				ret[i+2] = ' ';
			}
			
			// break from the main loop
			break;
		}
	}
	
	// copy the stack string into the main one
	ret[11] = 0;
	memcpy( str, ret, 12 );
	
	// return the string
	return str;
}

// checks that the bootsector in buf is a valid FAT bootsector
int is_fat_bootsector( unsigned char *buf )
{
	// temporary storage for bytes from the BPB
	unsigned temp;
	
	// return value
	int bad = 0;

	// is the first set of bytes valid? (must be a jmp/nop)
	if( ! ( ( buf[0] == 0xE9 ) || ( buf[0] == 0xEB && buf[2] == 0x90 ) ) )
		bad = -1; // not there, so bad bootsector
	
	// check that sectors per cluster is a power of 2
	temp = buf[13];
	if( is_power_2( temp ) < 0 )
		bad = -1; // nope
	
	// how many FATs - must be at least 1, maximum of 2
	temp = buf[16];
	if( temp != 1 && temp != 2 )
		bad = -1;
	
	// return to the caller
	return bad;
}

// finds a free cluster in the FAT
unsigned int find_free_clus( struct fat_fs fs )
{
	// search for it
	int j;
	unsigned int clus;
#if DEBUG
	dprintf( "Starting search from %d\n", (fs.fstype == FATFS_32 ? fs.fsinfo.FSI_Nxt_Free : 2) );
#endif
	for( j = (fs.fstype == FATFS_32 ? fs.fsinfo.FSI_Nxt_Free : 2); j < (fs.bpb.BPB_TotSec32 / fs.bpb.BPB_SecPerClus); j++ )
	{
		clus = get_clus_entry( fs, j );
#if DEBUG
		dprintf( "clus = %d\n", clus );
#endif
		if( clus == 0 )
		{
			// write it to the hard drive, once updating the "Next free" field
			int fd = sys_open( fs.mount, 0 );
			fs.fsinfo.FSI_Nxt_Free = j;
			sys_seek( fd, fs.bpb32.BPB_FsInfo, SEEK_SET );
			sys_read( fd, (char*) &fs.fsinfo, 1 );
			sys_close( fd );
			return j;
		}
	}
	return 0;
}

// creates a file (directories in FAT are nothing more than special files)
struct intdesc create_file( const char* filename, struct intdesc parent_directory, int fsnum, int isdir )
{
	// the return descriptor (defaults to invalid)
	struct intdesc ret;
	ret.valid = 0;
	
	// get the cluster of the directory in which to place the file
	unsigned int dirclus = parent_directory.dir_entry.DIR_FstClusLO | (parent_directory.dir_entry.DIR_FstClusHI << 16);
	
	// search for a zero offset and create the file when found
	switch( fats[fsnum].fstype )
	{
		case FATFS_12:
		case FATFS_16:
		
			// TODO: do these, which i've forgotten due to lack of codeage of these formats
			
			break;
		
		case FATFS_32:
			{
			
			// open the relevant device for reading
			int fd = sys_open( fats[fsnum].mount, 0 );
		
			// keep reading sectors and parsing until we find the entry or reach the end of the cluster chain
			unsigned int clus = dirclus;
			while( ! ( clus >= 0x0FFFFFF8 ) )
			{
				// read in the sector relevant for this entry
				struct stat s;
				sys_stat( fd, &s );
				char* dat = (char*) malloc( s.st_blksize * fats[fsnum].bpb.BPB_SecPerClus );
				if( !dat )
				{
					sys_close( fd );
					break;
				}
				sys_seek( fd, first_sector_of_cluster( fats[fsnum], clus ), SEEK_SET );
				sys_read( fd, dat, fats[fsnum].bpb.BPB_SecPerClus );
				
				// read the directory entries, try to find this file
				struct FAT_DirEnt* ents = (struct FAT_DirEnt*) dat;
				while( ents->DIR_Name[0] != 0 && (((char*)ents) < (dat + (s.st_blksize * fats[fsnum].bpb.BPB_SecPerClus))) ) { ents++; };
				if( ents->DIR_Name[0] == 0 )
				{					
					// if we need to create a directory, make it with the proper "." and ".." entries
					if( isdir )
					{
						// find a free cluster to use
						unsigned int freeclus = find_free_clus( fats[fsnum] );
						
						// check that it's valid
						if( freeclus == 0 )
						{
							// not valid return descriptor
							ret.valid = 0;
		
							// free the buffer
							free( dat );
							
							// close the device
							sys_close( fd );
							
							// return the descriptor
							return ret;
						}
						
						// set it to EOF straight away
						set_clus_entry( fats[fsnum], freeclus, 0x0FFFFFF8 );
						
						// create the first sector
						char* dirsec = (char*) malloc( s.st_blksize * 1 );
						if( !dirsec )
						{
							// not valid return descriptor
							ret.valid = 0;
		
							// free the buffer
							free( dat );
							
							// close the device
							sys_close( fd );
							
							// return the descriptor
							return ret;
						}
						
						// create the "." and ".." entries
						struct FAT_DirEnt* dotent = (struct FAT_DirEnt*) dirsec;
						struct FAT_DirEnt* dotdotent = (struct FAT_DirEnt*) (dirsec + sizeof( struct FAT_DirEnt ));
						
						// set each to zero
						memset( dotent, 0, sizeof( struct FAT_DirEnt ) );
						memset( dotdotent, 0, sizeof( struct FAT_DirEnt ) );
						
						// copy in filenames
						strcpy( dotent->DIR_Name, ".          " );
						strcpy( dotdotent->DIR_Name, "..         " );
						
						// setup attributes
						dotent->DIR_Attr = dotdotent->DIR_Attr = ATTR_DIRECTORY;
						
						// setup clusters
						dotent->DIR_FstClusLO = freeclus & 0xFFFF;
						dotent->DIR_FstClusHI = (freeclus >> 16) & 0xFFFF;
						dotdotent->DIR_FstClusLO = dirclus & 0xFFFF;
						dotdotent->DIR_FstClusHI = (dirclus >> 16) & 0xFFFF;
						
						// write it to the disk now that we're done
						sys_seek( fd, first_sector_of_cluster( fats[fsnum], freeclus ), SEEK_SET );
						sys_write( fd, dirsec, 1 );
						
						// set the parent entry to get the correct cluster for this directory
						ents->DIR_FstClusHI = (freeclus >> 16) & 0xFFFF;
						ents->DIR_FstClusLO = freeclus & 0xFFFF;
						
						// free the memory
						free( dirsec );
					}
					
					// setup the entry
					strcpy( ents->DIR_Name, filename );
					ents->DIR_Attr = (isdir ? ATTR_DIRECTORY : 0);
					// TODO: FAT timestamps
					ents->DIR_CrtTimeTenth = 0;
					ents->DIR_CrtTime = 0;
					ents->DIR_CrtDate = 0;
					ents->DIR_LstAccDate = 0;
					ents->DIR_WrtTime = 0;
					ents->DIR_WrtDate = 0;
					ents->DIR_FileSize = 0;
					if( !isdir )
					{
						ents->DIR_FstClusHI = 0;
						ents->DIR_FstClusLO = 0;
					}
					
					// write the cluster back to the device
					sys_seek( fd, first_sector_of_cluster( fats[fsnum], clus ), SEEK_SET );
					sys_write( fd, dat, fats[fsnum].bpb.BPB_SecPerClus );
					
					// set the descriptor up entry
					ret.valid = 1;
					ret.fsnum = fsnum;
					ret.desc = fd;
					ret.dir_sector = first_sector_of_cluster( fats[fsnum], clus );
					ret.dir_offset = ((unsigned int) ents) - ((unsigned int) dat);
					ret.dir_entry = *ents;
					
					// look for attributes, and zero the internal offset
					if( ret.dir_entry.DIR_Attr & ATTR_DIRECTORY )
						ret.isfile = 0;
					else
						ret.isfile = 1;
					ret.desc_offset = 0;
		
					// free the buffer
					free( dat );
					
					// close the device
					//sys_close( fd );
					
					// return the descriptor
					return ret;
				}
				
				// free the buffer
				free( dat );
				
				// get the next cluster entry
				clus = get_clus_entry( fats[fsnum], clus );
			}
				
			// close the device
			sys_close( fd );
			
			}
			break;
	}

	// return the descriptor
	return ret;
}

// deletes a file (fails if trying to delete a folder)
int unlink_file( int fsnum, struct intdesc desc )
{
	// fail if it's a directory
	if( desc.dir_entry.DIR_Attr & ATTR_DIRECTORY )
		return -1;
	
	// otherwise read in the relevant sector
	int fd = sys_open( fats[fsnum].mount, 0 );
	if( fd == -1 )
		return fd;
	struct stat s;
	sys_stat( fd, &s );
	char* dat = (char*) malloc( s.st_blksize );
	if( !dat )
	{
		sys_close( fd );
		return -1;
	}
	sys_seek( fd, desc.dir_sector, SEEK_SET );
	sys_read( fd, dat, 1 );
	
	// get the structure for this entry
	struct FAT_DirEnt* ent = (struct FAT_DirEnt*) (dat + desc.dir_offset);
	
	// make it an invalid entry
	ent->DIR_Name[0] = 0xE5;
	
	// write the entry back to the device
	sys_seek( fd, desc.dir_sector, SEEK_SET );
	sys_write( fd, dat, 1 );
	
	// and then unlink the cluster chain
	unsigned int clus = ent->DIR_FstClusLO | (ent->DIR_FstClusHI << 16);
	while( ! ( clus >= 0x0FFFFFF8 ) )
	{
		// get the next entry
		unsigned int prev = clus;
		clus = get_clus_entry( fats[fsnum], clus );
		
		// and then remove the one we were using before
		set_clus_entry( fats[fsnum], prev, 0 );
	}
	
	// clean up
	free( dat );
	sys_close( fd );
	
	// success!
	return 0;
}

// searches a directory for a file/directory (returns a FAT_DirEnt structure, from which you can get the cluster to pass)
struct intdesc search_directory( char* fn, int fsnum, unsigned int cluster )
{
	// search in the relevant directory for the relevant FS format
	struct intdesc ret;
	ret.valid = 0;
	switch( fats[fsnum].fstype )
	{
		case FATFS_12:
		case FATFS_16:
		
			// TODO: do these, which i've forgotten due to lack of codeage of these formats
			
			break;
		
		case FATFS_32:
			{
			
			// open the device for reading
#if DEBUG
			dprintf( "opening %s\n", fats[fsnum].mount );
#endif
			int fd = sys_open( fats[fsnum].mount, 0 );
			if( fd == -1 )
				return ret;
		
			// keep reading sectors and parsing until we find the entry or reach the end of the cluster chain
			unsigned int clus = cluster;
			while( clus < 0x0FFFFFF8 )
			{
				// read in the sector relevant for this entry
				struct stat s;
				sys_stat( fd, &s );
				char* dat = (char*) malloc( s.st_blksize * fats[fsnum].bpb.BPB_SecPerClus );
				if( !dat )
				{
					sys_close( fd );
					break;
				}
				sys_seek( fd, first_sector_of_cluster( fats[fsnum], clus ), SEEK_SET );
#if DEBUG
				dprintf( "about to read!\n" );
#endif
				int numread = sys_read( fd, dat, fats[fsnum].bpb.BPB_SecPerClus );
#if DEBUG
				dprintf( "read %d bytes\n", numread );
#endif
				
				// read the directory entries, try to find this file
				struct FAT_DirEnt* ents = (struct FAT_DirEnt*) dat;
				unsigned int offset = 0;
				while( ents->DIR_Name[0] != 0 && offset <= (s.st_blksize * numread) ) // (((char*)ents) < (dat + (s.st_blksize * /*fats[fsnum].bpb.BPB_SecPerClus*/ numread))) )
				{
					offset += sizeof(struct FAT_DirEnt);
					if( ents->DIR_Name[0] != 0xE5 )
					{
						// compare the strings
#if DEBUG
						dprintf( "comparing '%s' to '%s'...\n", ents->DIR_Name, fn );
#endif
						if( strncmp( fn, ents->DIR_Name, 11 ) == 0 )
						{
							// setup the descriptor
							ret.valid = 1;
							ret.fsnum = fsnum;
							ret.desc = fd; //-1;
#if DEBUG
							dprintf( "got one\n" );
#endif
							ret.dir_sector = first_sector_of_cluster( fats[fsnum], clus );
#if DEBUG
							dprintf( "got the first sector\n" );
#endif
							ret.dir_offset = ((unsigned int) ents) - ((unsigned int) dat);
							ret.dir_entry = *ents;
							
							// look for attributes, and zero the internal offset
							if( ret.dir_entry.DIR_Attr & ATTR_DIRECTORY )
								ret.isfile = 0;
							else
								ret.isfile = 1;
							ret.desc_offset = 0;
              
#if DEBUG
				            dprintf( "filename is %s\n", ents->DIR_Name );
#endif

							// free the buffer
#if DEBUG
							dprintf( "free at %x\n", free );
#endif
							free( dat );
              
#if DEBUG
							dprintf( "freed\n" );
#endif
							
							// close the device
							//sys_close( fd );
              
#if DEBUG
							dprintf( "returning\n" );
#endif
							
							// return the descriptor
							return ret;
						}
					}
					
					// go to the next entry in the list
#if DEBUG
					dprintf( "going to next entry\n" );
#endif
					ents++;
				}
				if( ents->DIR_Name[0] == 0 && offset < (s.st_blksize * numread) )
				{
#if DEBUG
					dprintf( "reached the end\n" );
#endif
          
					// free the buffer
					free( dat );
					
					// close the device
					sys_close( fd );
					
					// return the descriptor
					return ret;
				}
				
				// free the buffer
				free( dat );
				
				// get the next cluster entry
#if DEBUG
				dprintf( "getting a cluster entry\n" );
#endif
				clus = get_clus_entry( fats[fsnum], clus );
				dprintf("next clus = %x\n", clus);
#if DEBUG
				dprintf( "done!\n" );
#endif
			}
				
			// close the device
			sys_close( fd );
			
			}
			break;
	}
	
	// return to the caller
	return ret;
}

// opens a file for I/O
int fat_open( const char* path, int flags )
{
#if DEBUG
	dprintf( "fat_open( %s )\n", path );
#endif
  
	// find the filesystem to use
	int i;
	for( i = 0; i < 10; i++ )
	{
		if( fats[i].valid )
		{
			if( strncmp( fats[i].fsmount, path, strlen( fats[i].fsmount ) ) == 0 )
				break;
		}
	}
	if( i == 10 )
		return -1;
	
	// find a valid descriptor in the list
	int d;
	for( d = 0; d < 4096; d++ )
		if( desctable[d].valid == 0 )
			break;
	if( d == 4096 )
		return -1;
	
	// make sure that, if we do get pre-empted, we don't lose this descriptor
	desctable[d].valid = 1;
	desctable[d].openflags = flags;
	
	// we now have a valid filesystem, so parse the path into its individual parts
	unsigned int sz = strlen( path );
	char* edit_path = (char*) malloc( sz );
	strcpy( edit_path, path + strlen( fats[i].fsmount ) );
	if( edit_path[0] == 0 )
		strcpy( edit_path, "/" );
	
	// parse it into its individual parts, and find it in the filesystem
	int folder_count = 0;
	
	// find each slash in the string, which will allow us to find all the valid folder entries
	int z = 0;
	while( z < strlen( edit_path ) )
	{
		// is this a slash?
		if( edit_path[z] == '/' )
		{
			folder_count++;
		}
		z++;
	}
	
	// if the last character is a slash and there's only one folder, then we know that we're trying to open the root directory
	// which is a special case
	if( edit_path[strlen(edit_path) - 1] == '/' && folder_count == 1 )
	{
		// setup the descriptor
		unsigned int clus = fats[i].bpb32.BPB_RootClus;
		desctable[d].fsnum = i;
		desctable[d].desc = sys_open( fats[i].mount, 0 ); // -1
		desctable[d].isfile = 0;
		desctable[d].dir_sector = 0; // root directory has no parent
		desctable[d].dir_offset = 0;
		strcpy( desctable[d].dir_entry.DIR_Name, "" ); // no name for the root directory
		desctable[d].dir_entry.DIR_Attr = ATTR_DIRECTORY | ATTR_SYSTEM;
		desctable[d].dir_entry.DIR_FstClusHI = (fats[i].bpb32.BPB_RootClus >> 16) & 0xFFFF;
		desctable[d].dir_entry.DIR_FstClusLO = fats[i].bpb32.BPB_RootClus & 0xFFFF;
		desctable[d].desc_offset = 0;
		
		// free the path
		free(edit_path);

		// return the descriptor offset
		return d;
	}
		
	// save the old edit_path
	char* old_edit_path = edit_path;
	
	// check to see if the file is in the root directory
	int ret = 0; // what to return
	if( folder_count == 1 )
	{
		// the root directory
		char* rootdir;
		int rootdir_sz = 0;
		
		// skip over the slash
		edit_path++;
		
		// convert to an 8.3 filename
		edit_path = convert_to_8_3( edit_path );
		if( !edit_path ) // invalid filename much?
		{
			free( old_edit_path );
			return -1;
		}
		
		// grab the root directory, which is different depending on the FS type
		switch( fats[i].fstype )
		{
			case FATFS_12:
			case FATFS_16:
			
				// TODO: do these, which i've forgotten due to lack of codeage of these formats
				
				break;
			
			case FATFS_32:
				{
				
				// get the relevant information much?
#if DEBUG
				dprintf( "searching!\n" );
#endif
				desctable[d] = search_directory( edit_path, i, fats[i].bpb32.BPB_RootClus );
#if DEBUG
				dprintf( "done searching\n" );
#endif
				
				// if the file doesn't exist, and we're allowed to create files, create it
				if( (flags & O_CREAT) && (desctable[d].valid == 0) )
				{					
					// setup the descriptor to work as the root directory
					unsigned int clus = fats[i].bpb32.BPB_RootClus;
					desctable[d].fsnum = i;
					desctable[d].desc = -1;
					desctable[d].isfile = 0;
					desctable[d].dir_sector = 0; // root directory has no parent
					desctable[d].dir_offset = 0;
					strcpy( desctable[d].dir_entry.DIR_Name, "" ); // no name for the root directory
					desctable[d].dir_entry.DIR_Attr = ATTR_DIRECTORY | ATTR_SYSTEM;
					desctable[d].dir_entry.DIR_FstClusHI = (fats[i].bpb32.BPB_RootClus >> 16) & 0xFFFF;
					desctable[d].dir_entry.DIR_FstClusLO = fats[i].bpb32.BPB_RootClus & 0xFFFF;
					desctable[d].desc_offset = 0;
					
					// get the proper descriptor now
					desctable[d] = create_file( edit_path, desctable[d], i, 0 );
				}
				
				if( desctable[d].valid )
					ret = d;
				else
					ret = -1;
				
				}
				break;
		}
	}
	else
	{		
		// it isn't, so we need to check each folder for the file/folder
		char** parts = (char**) malloc( sizeof( char* ) * ( folder_count + 1 ) );
		
		// skip over the initial slash
		edit_path++;
		
		// go through, get each part pointer
		int y, last = 0, f = 0;
		int len = strlen( edit_path ); // we're shoving null bytes in the string, so this won't work properly
		for( y = 0; y < len; y++ )
		{
			// is this a slash?
			if( edit_path[y+1] == 0 )
				parts[f++] = (char*) (&edit_path[last]);
			if( edit_path[y] == '/' )
			{
				parts[f++] = (char*) (&edit_path[last]);
				edit_path[y] = 0;
				last = y + 1;
			}
		}
		
		// search for the file
		struct intdesc desc;
		desc.dir_entry.DIR_FstClusLO = fats[i].bpb32.BPB_RootClus & 0xFFFF;
		desc.dir_entry.DIR_FstClusHI = (fats[i].bpb32.BPB_RootClus >> 16) & 0xFFFF;
		ret = 0;
		for( y = 0; y < f; y++ )
		{
			// grab the 8.3 version of this section of the path
			char* realfn = (char*) malloc( 11 );
			char* old_realfn = realfn;
			memset( realfn, 0, 11 );
			memcpy( realfn, parts[y], strlen( parts[y] ) );
			realfn = convert_to_8_3( realfn );
			
			// search in the previous directory for this
			struct intdesc prev = desc;
			desc = search_directory( realfn, i, desc.dir_entry.DIR_FstClusLO | (desc.dir_entry.DIR_FstClusHI << 16) );
			
			// free the previously allocated memory
			free( old_realfn );
			
			// check that it's valid
			if( desc.valid == 0 )
			{
				// if the file doesn't exist, and we're allowed to create files, create it
				if( (flags & O_CREAT) && ((y+1) == f) )
				{					
					// get the proper descriptor now
					desc = create_file( realfn, prev, i, 0 );
					if( desc.valid == 0 )
						ret = -1; // invalid much?
				}
				else
				{
					// fail, we can't use this!
					ret = -1;
				}
				break;
			}
		}
		
		// if all went well, set the found descriptor as the internal one
		if( ret != -1 )
			desctable[d] = desc;
		
		// free that memory
		free( parts );
	}

	// free the editable path
	free( old_edit_path );
	
	// allow access to the descriptor again if we failed
	desctable[d].valid = (ret == -1 ? 0 : 1);
	
	// return whatever we found out
	return ret;
}

// reads a number of sectors from a drive
int fat_read( int fd, char* buff, int len )
{
	// check for a valid descriptor
	if( fd < 0 || fd >= 4096 || desctable[fd].valid == 0 )
		return -1;
	
#if DEBUG
	dprintf( "fat_read\n" );
#endif
	
	// if it's a directory, read in the next directory entry, unless it's null (in which case buff should be null too)
	if( desctable[fd].isfile == 0 )
	{
		// the length is in dirent blocks
		len /= (sizeof( struct dirent ));
		
		// read in every single dirent
		int dirent_num;
		for( dirent_num = 0; dirent_num < len; dirent_num++ )
		{		
			// start off by reading in the directory
			unsigned int clus = (desctable[fd].dir_entry.DIR_FstClusLO) | (desctable[fd].dir_entry.DIR_FstClusHI << 16);
			
			// the target entry to get
			unsigned int targent = desctable[fd].desc_offset;
			
			// keep reading sectors and parsing until we find the entry or reach the end of the cluster chain
			unsigned int clusnum = 0;
			int valid_ent_found = 0;
			struct FAT_DirEnt entry;
			while( clus < 0x0FFFFFF8 )
			{
				// read in the sector relevant for this entry
				int z = desctable[fd].desc; //sys_open( fats[desctable[fd].fsnum].mount, 0 );
				struct stat s;
				sys_stat( z, &s );
				char* dat = (char*) malloc( s.st_blksize * fats[desctable[fd].fsnum].bpb.BPB_SecPerClus );
				if( !dat )
				{
					sys_close( z );
					break;
				}
				unsigned int sec = first_sector_of_cluster( fats[desctable[fd].fsnum], clus );
				sys_seek( z, sec, SEEK_SET );
				int numread = sys_read( z, dat, fats[desctable[fd].fsnum].bpb.BPB_SecPerClus );
				//sys_close( z );
				
				// check to see if the directory entry is within this cluster
recheck:
				if(
					(targent * sizeof( struct FAT_DirEnt )) >= (clusnum * (fats[desctable[fd].fsnum].bpb.BPB_SecPerClus * fats[desctable[fd].fsnum].bpb.BPB_BytsPerSec))
					&&
					(targent * sizeof( struct FAT_DirEnt )) < ((clusnum+1) * (fats[desctable[fd].fsnum].bpb.BPB_SecPerClus * fats[desctable[fd].fsnum].bpb.BPB_BytsPerSec))
					)
				{
					// figure out the offset into the cluster itself
					unsigned int dat_offset = (targent * sizeof( struct FAT_DirEnt )) - (clusnum * (fats[desctable[fd].fsnum].bpb.BPB_SecPerClus * fats[desctable[fd].fsnum].bpb.BPB_BytsPerSec));
					
					// verify the entry first
					memcpy(&entry, &(dat[dat_offset]), sizeof(struct FAT_DirEnt));
					
					// entry = *((struct FAT_DirEnt*) (&dat[dat_offset]));
					if( entry.DIR_Name[0] == 0 )
					{
						// this is a special valid case
						valid_ent_found = 1;
						free( dat );
					}
					else if( ! ( entry.DIR_Attr & ATTR_VOLUME_ID ) && entry.DIR_Name[0] != 0xE5 )
					{
						// a valid entry!
						valid_ent_found = 1;
				
						// free the memory
						free( dat );
					}
					else
					{
						targent++;
						goto recheck;
					}
					break;
				}
				
				// free the memory
				free( dat );
				
				// another cluster done
				clusnum++;
				
				// get the next cluster entry
				clus = get_clus_entry( fats[desctable[fd].fsnum], clus );
			}
				
			// was a valid entry found?
			if( valid_ent_found )
			{
				// setup the internal offset
				if( targent == 0 )
					desctable[fd].desc_offset = 1;
				else
					desctable[fd].desc_offset = targent + 1;
				
				// return that we read nothing if we're at the end of the listing
				if(entry.DIR_Name[0] == 0)
				{
					return 0;
				}
				
				// create a dirent structure for it
				struct dirent d;
				d.d_off = (desctable[fd].desc_offset - 1) * sizeof( struct FAT_DirEnt );
				d.d_ino = entry.DIR_FstClusLO; // i think this is pretty smart
				
				// get the filename (convert from the 8.3 format FAT loves using)
				char* tmpfn = convert_from_8_3( entry.DIR_Name );
				d.d_namlen = strlen( tmpfn );
				memcpy( d.d_name, tmpfn, strlen( tmpfn ) + 1 );
				
				// copy it into the actual passed buffer
				memcpy( buff, &d, sizeof( struct dirent ) );
				
				// success!
				return sizeof( struct dirent );
			}
			else
			{
				if( clus >= 0x0FFFFFF8 )
					return 0; // end of the directory, but no NULL entry
				return -1; // otherwise an error occurred somewhere
			}
		}
	}
	else
	{
		// read it in
		int ret = fat_internal_sys_read_file( fats[desctable[fd].fsnum], desctable[fd], (char*) buff, len );
		if( ret != -1 )
			desctable[fd].desc_offset += ret;
		return ret;
	}
	
	// nothing read in, invalid file or something went wrong
	return -1;
}

// writes a number of sectors to a drive
int fat_write( int fd, char* buff, int len )
{
	// check for a valid descriptor
	if( fd < 0 || fd >= 4096 || desctable[fd].valid == 0 )
		return 0;
	
	// if it's a file, we can write, otherwise we can't
	if( desctable[fd].isfile == 1 )
	{
		// write the data to the file
#if DEBUG
		dprintf( "writing %d bytes\n", len );
#endif
		int ret = fat_internal_sys_write_file( fats[desctable[fd].fsnum], desctable[fd], buff, len );
#if DEBUG
		dprintf( "done\n" );
#endif
		return ret;
	}
	
	// failed!
	return -1;
}

// seeks to a new offset into the device
unsigned long fat_seek( int fd, unsigned long off, int set )
{
	// check for a valid descriptor
	if( fd < 0 || fd >= 4096 || desctable[fd].valid == 0 )
		return 0;

	// what is the "set"?
	switch( set )
	{
		case SEEK_SET:
			// set the new offset
			desctable[fd].desc_offset = off;
			if( desctable[fd].desc_offset > (desctable[fd].dir_entry.DIR_FileSize) )
				desctable[fd].desc_offset = desctable[fd].dir_entry.DIR_FileSize;
			break;
		case SEEK_CUR:
			// increment from the current offset
			desctable[fd].desc_offset += off;
			if( desctable[fd].desc_offset > (desctable[fd].dir_entry.DIR_FileSize) )
				desctable[fd].desc_offset = desctable[fd].dir_entry.DIR_FileSize;
			break;
		case SEEK_END:
			// increment from the current offset
			desctable[fd].desc_offset = desctable[fd].dir_entry.DIR_FileSize - off;
			if( desctable[fd].desc_offset < 0 )
				desctable[fd].desc_offset = 0;
			break;
	}
	
	// successfully seeked
	return desctable[fd].desc_offset;
}

// controls the device
int fat_ioctl( int fd, int action, int data )
{
	// check for a valid descriptor
	if( fd < 0 || fd >= 4096 || desctable[fd].valid == 0 )
		return -1;
	
	// nothing to do here
	return 0;
}

// gets information about a specific open descriptor
int fat_stat( int fd, struct stat* buf )
{
	// check for a valid descriptor
	if( fd < 0 || fd >= 4096 || desctable[fd].valid == 0 )
		return -1;
	
	// fill in the relevant information
	buf->st_size = desctable[fd].dir_entry.DIR_FileSize;
	buf->st_blksize = 1;
	buf->st_mode = (desctable[fd].isfile ? _IFREG : _IFDIR);
	buf->st_atime = desctable[fd].dir_entry.DIR_LstAccDate;
	buf->st_mtime = desctable[fd].dir_entry.DIR_LstAccDate;
	buf->st_ctime = desctable[fd].dir_entry.DIR_CrtTime;

	// success
	return 0;
}

// closes a descriptor
int fat_close( int fd )
{
	// check for a valid descriptor
	if( fd < 0 || fd >= 4096 || desctable[fd].valid == 0 )
		return -1;
	
	// make the descriptor invalid now
	if(desctable[fd].desc >= 0)
		sys_close( desctable[fd].desc );
	desctable[fd].valid = 0;
	
	// successful close
	return 0;
}

// installs the driver and sets it up properly
void dmain( int argc, char* argv[] )
{
	// clear out the fat_fs and desctable
	memset( fats, 0, sizeof( struct fat_fs ) * 10 );
	memset( desctable, 0, sizeof( struct intdesc ) * 4096 );
	
	// read in bootsectors and find out if they're valid FAT bootsectors
	int fd = 0, devid = 0;
	while( fd != -1 && fd < 10 )
	{
		// build the device string
		char d[32];
		sprintf( d, "/dev/hd%d", devid );
		
		// TODO: MBR support
		
		// attempt to open the device, and read in the first sector
		fd = fats[devid].desc = sys_open( d, 0 );
		sys_seek( fd, 0, SEEK_SET );
		char tmp[512];
		sys_read( fd, tmp, 1 );
		
		// check that tmp will be valid
		if( fd == -1 )
			continue;
		
		// verify it
		if( is_fat_bootsector( (unsigned char*) tmp ) == 0 )
		{
			// install the filesystem into the fs table
			fats[devid].valid = 1;
			strcpy( fats[devid].mount, d );
			fats[devid].fstype = 0; // 0 = invalid FAT version
			fats[devid].bpb = *((struct BPB_FAT*) tmp);
			fats[devid].bpb32 = *((struct BPB_FAT32*) (tmp + 36));
			fats[devid].bpb16_12 = *((struct BPB_FAT16_12*) (tmp + 36));
			
			// calculate the start of the data area and stuff like that....
			unsigned int RootDirSectors = ((fats[devid].bpb.BPB_RootEntCnt * 32) + (fats[devid].bpb.BPB_BytsPerSec - 1)) / fats[devid].bpb.BPB_BytsPerSec;
			
			// calculate the size of the FAT
			unsigned int FATSz = 0;
			if( fats[devid].bpb.BPB_FATSz16 != 0 )
				FATSz = fats[devid].bpb.BPB_FATSz16;
			else
				FATSz = fats[devid].bpb32.BPB_FATSz32;
			
			// get the first data sector
			unsigned int FirstDataSector = fats[devid].bpb.BPB_RsvdSecCnt + (fats[devid].bpb.BPB_NumFATs * FATSz) + RootDirSectors;
			
			// get the total number of data sectors
			unsigned int TotSec = 0, DataSec = 0;
			if( fats[devid].bpb.BPB_TotSec16 != 0 )
				TotSec = fats[devid].bpb.BPB_TotSec16;
			else
				TotSec = fats[devid].bpb.BPB_TotSec32;
			DataSec = TotSec - (fats[devid].bpb.BPB_RsvdSecCnt + (fats[devid].bpb.BPB_NumFATs * FATSz) + RootDirSectors);
			
			// get the cluster count
			unsigned int CountofClusters = DataSec / fats[devid].bpb.BPB_SecPerClus;
			
			// figure out which FS it is
			if( CountofClusters < 4085 )
				fats[devid].fstype = FATFS_12;
			else if( CountofClusters < 65525 )
				fats[devid].fstype = FATFS_16;
			else
				fats[devid].fstype = FATFS_32;
			
			// fill in the rest of the structure
			fats[devid].data_area_start = FirstDataSector;
			fats[devid].root_dir_count = RootDirSectors;
			
			// setup the FSInfo structure
			if( fats[devid].fstype == FATFS_32 )
			{
				// read it in
				sys_seek( fd, fats[devid].bpb32.BPB_FsInfo, SEEK_SET );
				sys_read( fd, tmp, 1 );
				
				// set it
				fats[devid].fsinfo = *((struct FAT_FSINFO32*) tmp);
			}

			// print this information to the debugger
#if DEBUG
			dprintf( "%s is FAT%s, %d root sectors, %d first data sector, %d data sectors\n", fats[devid].mount, (fats[devid].fstype == FATFS_12 ? "12" : (fats[devid].fstype == FATFS_16 ? "16" : "32")), RootDirSectors, FirstDataSector, DataSec );
#endif
			
			// install it as a device
			char devname[32];
			sprintf( devname, "/hd%d", devid );
			strcpy( fats[devid].fsmount, devname );
			InstallDevice( devname, (unsigned int) fat_open, (unsigned int) fat_read, (unsigned int) fat_write, (unsigned int) fat_seek, (unsigned int) fat_ioctl, (unsigned int) fat_stat, (unsigned int) fat_close, sizeof( char ) );
		}
		
		// close the descriptor now
		sys_close( fd );
		fats[devid].desc = -1;
		
		// another device entry
		devid++;
	}
}
